var __importDefault =
    (this && this.__importDefault) ||
    ((mod) => (mod?.__esModule ? mod : { default: mod }));
Object.defineProperty(exports, "__esModule", { value: true });
exports.Deobfuscator = void 0;
const generator_1 = __importDefault(require("@babel/generator"));
const traverse_1 = __importDefault(require("@babel/traverse"));
const config_1 = require("./transformations/config");
const objectSimplifier_1 = require("./transformations/objects/objectSimplifier");
const proxyFunctionInliner_1 = require("./transformations/proxyFunctions/proxyFunctionInliner");
const unusedVariableRemover_1 = require("./transformations/variables/unusedVariableRemover");
const constantPropagator_1 = require("./transformations/variables/constantPropagator");
const reassignmentRemover_1 = require("./transformations/variables/reassignmentRemover");
const stringRevealer_1 = require("./transformations/strings/stringRevealer");
const deadBranchRemover_1 = require("./transformations/controlFlow/deadBranchRemover");
const sequenceSplitter_1 = require("./transformations/controlFlow/sequenceSplitter");
const propertySimplifier_1 = require("./transformations/properties/propertySimplifier");
const expressionSimplifier_1 = require("./transformations/expressions/expressionSimplifier");
const controlFlowRecoverer_1 = require("./transformations/controlFlow/controlFlowRecoverer");
const objectPacker_1 = require("./transformations/objects/objectPacker");
class Deobfuscator {
    /**
     * Creates a new deobfuscator.
     * @param ast The AST.
     * @param config The config (optional).
     */
    constructor(ast, config = config_1.defaultConfig) {
        this.transformationTypes = [
            unusedVariableRemover_1.UnusedVariableRemover,
            constantPropagator_1.ConstantPropgator,
            reassignmentRemover_1.ReassignmentRemover,
            deadBranchRemover_1.DeadBranchRemover,
            objectPacker_1.ObjectPacker,
            proxyFunctionInliner_1.ProxyFunctionInliner,
            expressionSimplifier_1.ExpressionSimplifier,
            sequenceSplitter_1.SequenceSplitter,
            controlFlowRecoverer_1.ControlFlowRecoverer,
            propertySimplifier_1.PropertySimplifier,
            objectSimplifier_1.ObjectSimplifier,
            stringRevealer_1.StringRevealer,
        ];
        this.ast = ast;
        this.config = config;
    }
    /**
     * Executes the deobfuscator.
     * @returns The simplified code.
     */
    execute() {
        const types = this.transformationTypes.filter(
            (t) => this.config[t.properties.key].isEnabled,
        );
        let i = 0;
        while (i < Deobfuscator.MAX_ITERATIONS) {
            let isModified = false;
            if (!this.config.silent) {
                console.log(
                    `\n[${new Date().toISOString()}]: Starting pass ${i + 1}`,
                );
            }
            for (const type of types) {
                const transformationConfig = this.config[type.properties.key];
                const transformation = new type(this.ast, transformationConfig);
                if (!this.config.silent) {
                    console.log(
                        `[${new Date().toISOString()}]: Executing ${transformation.constructor.name}`,
                    );
                }
                let modified = false;
                try {
                    modified = transformation.execute(
                        console.log.bind(
                            console,
                            `[${transformation.constructor.name}]:`,
                        ),
                    );
                } catch (err) {
                    console.log(`Error: ${err}`);
                }
                if (modified) {
                    isModified = true;
                }
                if (!this.config.silent) {
                    console.log(
                        `[${new Date().toISOString()}]: Executed ${transformation.constructor.name}, modified ${modified}`,
                    );
                }
                if (type.properties.rebuildScopeTree) {
                    this.clearCache();
                }
            }
            i++;
            if (!isModified) {
                break;
            }
        }
        return (0, generator_1.default)(this.ast, {
            jsescOption: { minimal: true },
        }).code;
    }
    /**
     * Clears the traversal cache to force the scoping to be handled
     * again on the next traverse.
     */
    clearCache() {
        traverse_1.default.cache.clear();
    }
}
exports.Deobfuscator = Deobfuscator;
Deobfuscator.MAX_ITERATIONS = 50;
